
module ArtOfRails
  module Rdf
    module ActiveRecord    
      class RdfSerializer #:nodoc:
        
        class Attribute #:nodoc:
          attr_reader :name, :value, :type, :property

          def initialize(name, record)
            @name = name
            @record = record
            property = @record.class.field_to_rdf_property(@name)

            if property.is_a? String
              @property = property
            else
              @property = property[:ns].to_s + ":" + property[:local_name].to_s
            end            
            @type  = compute_type
            @value = compute_value
          end

          # There is a significant speed improvement if the value
          # does not need to be escaped, as #tag! escapes all values
          # to ensure that valid XML is generated.  For known binary
          # values, it is at least an order of magnitude faster to
          # Base64 encode binary values and directly put them in the
          # output XML than to pass the original value or the Base64
          # encoded value to the #tag! method. It definitely makes
          # no sense to Base64 encode the value and then give it to
          # #tag!, since that just adds additional overhead.
          def needs_encoding?
            ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
          end

          def decorations(include_types = true)
            decorations = {}

            if type == :binary
              decorations[:encoding] = 'base64'
            end

            if include_types && type != :string
              decorations[:"rdf:datatype"] = xsd_type(type)
            end

            decorations
          end

          protected
            def compute_type
              type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type

              case type
                when :text
                  :string
                when :time
                  :datetime
                else
                  type
              end
            end

            def xsd_type(coltype)
              types = {
                :binary => 'http://www.w3.org/2001/XMLSchema#hexBinary', 
                :date => 'http://www.w3.org/2001/XMLSchema#date', 
                :datetime => 'http://www.w3.org/2001/XMLSchema#dateTime', 
                :boolean => 'http://www.w3.org/2001/XMLSchema#boolean', 
                :float => 'http://www.w3.org/2001/XMLSchema#float', 
                :integer => 'http://www.w3.org/2001/XMLSchema#int'
              }
              return types[coltype] if types[coltype]
              return coltype
            end

            def compute_property
              property = @record.class.get_rdf_property_for @name
              return property
            end

            def compute_value
              value = @record.send(name)

              if formatter = Hash::XML_FORMATTING[type.to_s]
                value ? formatter.call(value) : nil
              else
                value
              end
            end
        end

        class MethodAttribute < Attribute #:nodoc:
          def initialize(name, record, property)
            @name = name
            @record = record
            @property = property
            @type  = compute_type
            @value = compute_value            
          end

          protected
            def compute_type
              Hash::XML_TYPE_NAMES[@record.send(name).class.name] || :string
            end
        end
        
        class IndividualAttribute < Attribute #:nodoc:
          attr_reader :style, :object, :options
          
          def initialize(name, record, property, object, options)
            default_options = {:style => :reference}
            @options = default_options.update(options)
            @name = name
            @record = record
            @property = property
            @object = object
            @value = compute_value            
            @style = @options[:style]            
            @options[:include] = [] unless @options[:include] 
          end
        
          protected
            def compute_value
              @object.resource_uri
            end
        end
        
      end
    end
  end
end